package com.wmh.baseservice.robot.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.wmh.baseservice.admin.entity.AdInfo;
import com.wmh.baseservice.admin.service.AdInfoService;
import com.wmh.baseservice.common.ex.ServiceException;
import com.wmh.baseservice.common.job.quartz.JobHandler;
import com.wmh.baseservice.robot.cache.RobotJobTaskRepository;
import com.wmh.baseservice.robot.cache.dto.JobTaskFailDelayCacheDTO;
import com.wmh.baseservice.robot.convert.RobotTaskAdPushUnitConvert;
import com.wmh.baseservice.robot.entity.RobotInfo;
import com.wmh.baseservice.robot.entity.RobotJobTask;
import com.wmh.baseservice.robot.entity.RobotRes;
import com.wmh.baseservice.robot.entity.RobotTaskAdPushUnit;
import com.wmh.baseservice.robot.enums.RobotJobTaskStatus;
import com.wmh.baseservice.robot.enums.RobotPushTaskType;
import com.wmh.baseservice.robot.enums.RobotStatusEnums;
import com.wmh.baseservice.robot.pojo.dto.robottaskadpushunit.RobotTaskAdPushUnitPushReqVO;
import com.wmh.baseservice.robot.service.*;
import com.wmh.baseservice.robot.tools.MyHttpUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

import static cn.hutool.core.text.CharSequenceUtil.format;

/**
 * @author 谢骏
 * @date 2023/8/23 11:38
 **/
@Component
@Slf4j
public class TaskJobHandler implements JobHandler {

    public static final String NAME = StrUtil.lowerFirst(TaskJobHandler.class.getSimpleName());

    @Resource
    private MyHttpUtil myHttpUtil;

    @Resource
    private AdInfoService adInfoService;

    @Resource
    private RobotInfoService robotInfoService;

    @Resource
    private RobotTaskService robotTaskService;

    @Resource
    private RobotJobTaskService robotJobTaskService;

    @Resource
    private RobotTaskAdPushUnitService robotTaskAdPushUnitService;

    @Resource
    private RobotTaskStatementV2Service robotTaskStatementV2Service;

    @Resource
    private RobotJobTaskRepository robotJobTaskRepository;

    @Override
    public String execute(Long jobId, String jsonParam) throws Exception {
        JSONObject param = JSON.parseObject(jsonParam);
        log.info("任务id : {} 任务携带参数 : {} 时间 : {}", jobId, param, LocalDateTimeUtil.formatNormal(LocalDateTimeUtil.now()));
        // 获取定时任务
        RobotJobTask job = robotJobTaskService.getById(jobId);
        if (job == null) {
            robotJobTaskService.delete(jobId);
            throw new ServiceException(format("定时任务已不存在 : {}", jobId));
        }
        StringBuilder errMsg = pushTask(job);
        if (StrUtil.isNotBlank(errMsg)) {
            noRobotHandler(job, param);
        }
        return null;
    }

    /**
     * 发布任务
     *
     * @param job 定时任务
     * @return 任务信息
     */
    @Transactional(rollbackFor = Exception.class)
    public StringBuilder pushTask(RobotJobTask job) {
        StringBuilder errMsg = new StringBuilder();
        // 获取发布单元信息
        List<RobotTaskAdPushUnit> unitList = robotTaskAdPushUnitService.listByIds(job.getPushTaskIds());
        // 获取闲置的机器
        List<RobotInfo> robots = robotInfoService.getRobotInfo(RobotStatusEnums.LEAVE_UNUSED.getId());
        // 无闲置机器
        if (CollUtil.isEmpty(robots)) {
            return errMsg.append("机器任务-定时执行模块 -> 当前没有闲置机器");
        }
        // 管理员信息
        AdInfo adInfo = adInfoService.getById(job.getAdId());
        // 发布管理员若不存在处理
        adInfoNoExistHandler(adInfo, job);
        // 获取发布单元对应的任务名称与主键的映射 方便获取任务名称
        Map<Long, String> nameMap = robotTaskService.getIdNameMap(CollUtil.map(unitList, RobotTaskAdPushUnit::getRobotTaskId, true));
        // 构建发布任务的参数
        RobotTaskAdPushUnitPushReqVO reqObj = RobotTaskAdPushUnitConvert.INSTANCE.convert1(unitList, nameMap, adInfo, false, RobotPushTaskType.JOB.getValue());
        // 使用的机器
        RobotInfo useRobot = null;
        for (RobotInfo robot : robots) {
            RobotRes res = myHttpUtil.post(robot, "接收任务", JSON.parseObject(JSON.toJSONString(reqObj)));
            // 任务发布成功
            if (!RobotRes.isError(res)) {
                log.info("发布定时任务请求成功 job : {} \n robot : {} \n reqObj : {}", job, robot, reqObj);
                useRobot = robot.setStatusId(RobotStatusEnums.RUNNING.getId());
                break;
            } else {
                log.warn("发布定时任务请求失败 errMsg: {} job : {} \n robot : {} \n reqObj: {}", res.getMessage(), job, robot, reqObj);
            }
        }
        // 没有任何机器接收到任务
        if (useRobot == null) {
            return errMsg.append("机器任务-定时执行模块 -> 没有任何机器接收任务成功");
        }
        robotTaskStatementV2Service.create(reqObj, useRobot);
        return errMsg;
    }

    private void noRobotHandler(RobotJobTask job, JSONObject param) {
        JobTaskFailDelayCacheDTO value = buildCacheDTO(job, param);
        // todo: 暂时默认10分钟重发一次
        robotJobTaskRepository.setFailDelay(value.getTimestamp(), 10, value);
    }

    private JobTaskFailDelayCacheDTO buildCacheDTO(RobotJobTask job, JSONObject param) {
        JobTaskFailDelayCacheDTO r = new JobTaskFailDelayCacheDTO()
                .setJobId(job.getId());
        // 定义任务初次失败时的时间戳
        if (param != null) {
            Long timestamp = param.getLong("timestamp");
            Integer retryCount = param.getInteger("retryCount");
            r.setRetryCount(retryCount != null ? retryCount + 1 : 0);
            r.setTimestamp(timestamp != null ? timestamp : System.currentTimeMillis());
        } else {
            r.setTimestamp(System.currentTimeMillis());
            r.setRetryCount(0);
        }
        return r;
    }

    @SneakyThrows
    private void adInfoNoExistHandler(AdInfo adInfo, RobotJobTask jobTask) {
        if (adInfo == null) {
            robotJobTaskService.updateStatus(jobTask.getId(), RobotJobTaskStatus.PAUSING.getValue());
            throw new ServiceException(StrUtil.format("该管理员已不存在 编号 : {}", jobTask.getAdId()));
        }
    }
}
